using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using FwkLight.CodeExtensions;

namespace FwkLight.Domain.Fetching
{
  public class FetchingPart<T>: IFetchingPart<T>
  {
    private readonly IList<IFetchingPart> _children;
    private readonly IFetchingPart _parent;
    private readonly string _alias;
    private readonly Type _partType;
    private readonly string _propertyName;
    private readonly bool _isList;
    private readonly string _parentPropertyName;
    private readonly bool _isRoot;
    private readonly IEntityAliasParser _entityAliasParser;
    private readonly AliasListForHierarchy _aliasListForHierarchy;
    private readonly bool _isInverse;
    public int ChangesCount { get; private set; }

    public bool IsInverse
    {
      get { return _isInverse; }
    }

    public IList<IFullSpecification<T>> Specifications { get; set; }
		public IList<SpecificationWithOneParam> SpecificationsWithOneParam { get; set; }
    public IList<SpecificationWithTwoParams> SpecificationsWithTwoParams { get; set; }
    public IList<SpecificationWithThreeParams> SpecificationsWithThreeParams { get; set; }
    public IList<SpecificationWithFourParams> SpecificationsWithFourParams { get; set; }
    public IList<ISortingPart> SortingList { get; set; }

    public bool IsRoot
    {
      get { return _isRoot; }
    }

    public string ParentPropertyName
    {
      get { return _parentPropertyName; }
    }

    public IFetchingPart<R> BackTo<R>()
    {
      return BackTo<R>(_entityAliasParser.ParseAliasFor(typeof(R), null));
    }

    public IList<IFetchingFilter> Filters { get; set; }
    public IFetchingPart<R> WhichAlsoContainsListFromComponent<S, R>(Expression<Func<T, S>> componentProperty, Expression<Func<S, IEnumerable<R>>> listProperty, Expression<Func<R, T>> parentProperty)
    {
      return WhichAlsoContainsListFromComponent(componentProperty, listProperty,
                                                      _entityAliasParser.ParseAliasFor(typeof(R), this.CalculateThe(_aliasListForHierarchy)), parentProperty);
    }

    public FetchingPart(IFetchingPart parent, string alias, Type partType, string propertyName, bool isList, string parentPropertyName, bool isRoot, bool isInverse)
    {
      _parent = parent;
      _alias = alias;
      _isList = isList;
      _parentPropertyName = parentPropertyName;
      _isRoot = isRoot;
      _partType = partType;
      _propertyName = propertyName;
      _children = new List<IFetchingPart>();
      Filters = new List<IFetchingFilter>();
      Specifications = new List<IFullSpecification<T>>();
			SpecificationsWithOneParam = new List<SpecificationWithOneParam>();
      SpecificationsWithTwoParams = new List<SpecificationWithTwoParams>();
      SpecificationsWithThreeParams = new List<SpecificationWithThreeParams>();
      SpecificationsWithFourParams = new List<SpecificationWithFourParams>();
      SortingList = new List<ISortingPart>();

      _entityAliasParser = new EntityAliasParser();
      _aliasListForHierarchy = new AliasListForHierarchy(new RootFetchingPartInHierarchy());

      _isInverse = isInverse;
    }

    public IEnumerable<IFetchingPart> Children
    {
      get { return _children; }
    }

    public bool IsList
    {
      get { return _isList; }
    }

    public IFetchingPart<R> WhichContainsList<R>(Expression<Func<T, IEnumerable<R>>> property, string alias)
    {
      return AddListFetchingPart(property, alias, null);
    }

    public IFetchingPart<R> WhichAlsoContainsList<R>(Expression<Func<T, IEnumerable<R>>> property, string alias)
    {
      return AddListFetchingPart(property, alias, null);
    }

    public IFetchingPart<R> WhichContainsList<R>(Expression<Func<T, IEnumerable<R>>> property, string alias, Expression<Func<R, T>> parentProperty)
    {
      return AddListFetchingPart(property, alias, parentProperty);
    }

    public IFetchingPart<R> WhichContainsListWithListParent<R>(Expression<Func<T, IEnumerable<R>>> property, string alias, Expression<Func<R, IList<T>>> parentProperty)
    {
      return AddListFetchingPartWithListParent(property, alias, parentProperty);
    }

    private string RetrieveNameFrom<T, R>(Expression<Func<T, R>> expression)
    {
      if (expression != null)
      {
        MemberInfo info = ((MemberExpression)expression.Body).Member;
        return info.Name;
      }

      return null;
    }

    private IFetchingPart<R> AddListFetchingPart<R>(Expression<Func<T, IEnumerable<R>>> property, string alias, Expression<Func<R, T>> parentProperty)
    {
      return AddFetchingPart<R>(RetrieveNameFrom(property), alias, RetrieveNameFrom(parentProperty), true, false);
    }

    private IFetchingPart<R> AddListFetchingPartWithListParent<R>(Expression<Func<T, IEnumerable<R>>> property, string alias, Expression<Func<R, IList<T>>> parentProperty)
    {
      return AddFetchingPart<R>(RetrieveNameFrom(property), alias, RetrieveNameFrom(parentProperty), true, false);
    }

    private IFetchingPart<R> AddListFetchingPart<S, R>(Expression<Func<T, S>> componentProperty, Expression<Func<S, IEnumerable<R>>> listProperty, string alias, Expression<Func<R, T>> parentProperty)
    {
      return AddFetchingPart<R>(RetrieveNameFrom(componentProperty) + "." + RetrieveNameFrom(listProperty), alias, RetrieveNameFrom(parentProperty), true, false);
    }

    public IFetchingPart<R> WhichAlsoContainsList<R>(Expression<Func<T, IEnumerable<R>>> property, string alias, Expression<Func<R, T>> parentProperty)
    {
      return AddListFetchingPart(property, alias, parentProperty);
    }

    public IFetchingPart<R> WhichContainsEntity<R>(Expression<Func<T, R>> property, string alias)
    {
      return AddEntityFetchingPart(property, alias, null);
    }

    public IFetchingPart<R> WhichContainsEntity<R>(Expression<Func<T, R>> property, string alias, Expression<Func<R, T>> parentProperty)
    {
      return AddEntityFetchingPart(property, alias, parentProperty);
    }

    public IFetchingPart<R> WhichContainsInverseEntity<R>(Expression<Func<T, R>> property, string alias)
    {
      return AddInverseEntityFetchingPart(property, alias, property);
    }

    private IFetchingPart<R> AddInverseEntityFetchingPart<R>(Expression<Func<T, R>> property, string alias, Expression<Func<T, R>> parentProperty)
    {
      return AddFetchingPart<R>(RetrieveNameFrom(property), alias, RetrieveNameFrom(parentProperty), false, true);
    }

    public IFetchingPart<R> WhichAlsoContainsEntity<R>(Expression<Func<T, R>> property, string alias)
    {
      return AddEntityFetchingPart(property, alias, null);
    }

    public IFetchingPart<R> InWhichEveryElementContains<R>(Expression<Func<T, R>> property, string alias)
    {
      return AddEntityFetchingPart(property, alias, null);
    }

    public IFetchingPart<R> WhichContainsList<R>(Expression<Func<T, IEnumerable<R>>> property, Expression<Func<R, T>> parentProperty)
    {
      return WhichContainsList(property, _entityAliasParser.ParseAliasFor(typeof(R), this.CalculateThe(_aliasListForHierarchy)), parentProperty);
    }

    public IFetchingPart<R> WhichContainsListWithListParent<R>(Expression<Func<T, IEnumerable<R>>> property, Expression<Func<R, IList<T>>> parentProperty)
    {
      return WhichContainsListWithListParent(property, _entityAliasParser.ParseAliasFor(typeof(R), this.CalculateThe(_aliasListForHierarchy)), parentProperty);
    }

    public IFetchingPart<R> WhichAlsoContainsList<R>(Expression<Func<T, IEnumerable<R>>> property, Expression<Func<R, T>> parentProperty)
    {
      return WhichAlsoContainsList(property, _entityAliasParser.ParseAliasFor(typeof(R), this.CalculateThe(_aliasListForHierarchy)), parentProperty);
    }

    public IFetchingPart<R> WhichContainsList<R>(Expression<Func<T, IEnumerable<R>>> property)
    {
      return WhichContainsList(property, _entityAliasParser.ParseAliasFor(typeof(R), this.CalculateThe(_aliasListForHierarchy)));
    }

    public IFetchingPart<R> WhichAlsoContainsList<R>(Expression<Func<T, IEnumerable<R>>> property)
    {
      return WhichAlsoContainsList(property, _entityAliasParser.ParseAliasFor(typeof(R), this.CalculateThe(_aliasListForHierarchy)));
    }

    public IFetchingPart<R> WhichContainsEntity<R>(Expression<Func<T, R>> property)
    {
      return WhichContainsEntity(property, _entityAliasParser.ParseAliasFor(typeof(R), this.CalculateThe(_aliasListForHierarchy)));
    }

    public IFetchingPart<R> WhichContainsEntity<R>(Expression<Func<T, R>> property, Expression<Func<R, T>> parentProperty)
    {
      return WhichContainsEntity(property, _entityAliasParser.ParseAliasFor(typeof(R), this.CalculateThe(_aliasListForHierarchy)), parentProperty);
    }

    public IFetchingPart<R> WhichContainsInverseEntity<R>(Expression<Func<T, R>> property)
    {
      return WhichContainsInverseEntity(property, _entityAliasParser.ParseAliasFor(typeof(R), this.CalculateThe(_aliasListForHierarchy)));
    }

    public IFetchingPart<R> WhichAlsoContainsEntity<R>(Expression<Func<T, R>> property)
    {
      return WhichAlsoContainsEntity(property, _entityAliasParser.ParseAliasFor(typeof(R), this.CalculateThe(_aliasListForHierarchy)));
    }

    public IFetchingPart<R> WhichAlsoContainsEntity<R>(Expression<Func<T, R>> property, Expression<Func<R, T>> parentProperty)
    {
      return WhichAlsoContainsEntity(property,
                                     _entityAliasParser.ParseAliasFor(typeof (R),
                                                                      this.CalculateThe(_aliasListForHierarchy)),
                                     parentProperty);
    }

    public IFetchingPart<R> WhichAlsoContainsEntity<R>(Expression<Func<T, R>> property, string alias, Expression<Func<R, IEnumerable<T>>> parentProperty)
    {
      return AddEntityFetchingPartForListParent(property, alias, parentProperty);
    }

    public IFetchingPart<R> WhichAlsoContainsEntity<R>(Expression<Func<T, R>> property, Expression<Func<R, IEnumerable<T>>> parentProperty)
    {
      return WhichAlsoContainsEntity(property, _entityAliasParser.ParseAliasFor(typeof(R),
                                                                      this.CalculateThe(_aliasListForHierarchy)), parentProperty);
    }

    public IFetchingPart<R> WhichAlsoContainsEntity<R>(Expression<Func<T, R>> property, string alias, Expression<Func<R, T>> parentProperty)
    {
      return AddEntityFetchingPart(property, alias, parentProperty);
    }

    public IFetchingPart<R> InWhichEveryElementContains<R>(Expression<Func<T, R>> property)
    {
      return InWhichEveryElementContains(property, _entityAliasParser.ParseAliasFor(typeof(R), this.CalculateThe(_aliasListForHierarchy)));
    }

    public IFetchingPart<R> WhichAlsoContainsListFromComponent<S, R>(Expression<Func<T, S>> componentProperty, Expression<Func<S, IEnumerable<R>>> listProperty, string alias)
    {
      return AddListFetchingPart(componentProperty, listProperty, alias, null);
    }

    public IFetchingPart<R> WhichAlsoContainsListFromComponent<S, R>(Expression<Func<T, S>> componentProperty, Expression<Func<S, IEnumerable<R>>> listProperty)
    {
      return WhichAlsoContainsListFromComponent(componentProperty, listProperty,
                                                      _entityAliasParser.ParseAliasFor(typeof(R), this.CalculateThe(_aliasListForHierarchy)));
    }

    public IFetchingPart<R> WhichAlsoContainsEntityFromComponent<S, R>(Expression<Func<T, S>> componentProperty, Expression<Func<S, R>> listProperty)
    {
      return WhichAlsoContainsEntityFromComponent(componentProperty, listProperty,
                                                      _entityAliasParser.ParseAliasFor(typeof(R), this.CalculateThe(_aliasListForHierarchy)));
    }

    public IFetchingPart<R> WhichAlsoContainsEntityFromComponent<S, R>(Expression<Func<T, S>> componentProperty, Expression<Func<S, R>> property, string alias)
    {
      return AddEntityFetchingPart(componentProperty, property, alias, null);
    }

    public IFetchingPart<R> WhichAlsoContainsListFromComponent<S, R>(Expression<Func<T, S>> componentProperty, Expression<Func<S, IEnumerable<R>>> listProperty, string alias, Expression<Func<R, T>> parentProperty)
    {
      return AddListFetchingPart(componentProperty, listProperty, alias, parentProperty);
    }

    private IFetchingPart<R> AddEntityFetchingPart<R>(Expression<Func<T, R>> property, string alias, Expression<Func<R, T>> parentProperty)
    {
      return AddFetchingPart<R>(RetrieveNameFrom(property), alias, RetrieveNameFrom(parentProperty), false, false);
    }

    private IFetchingPart<R> AddEntityFetchingPart<S, R>(Expression<Func<T, S>> componentProperty, Expression<Func<S, R>> property, string alias, Expression<Func<R, T>> parentProperty)
    {
      return AddFetchingPart<R>(RetrieveNameFrom(componentProperty) + "." + RetrieveNameFrom(property), alias, RetrieveNameFrom(parentProperty), true, false);
    }

    private IFetchingPart<R> AddEntityFetchingPartForListParent<R>(Expression<Func<T, R>> property, string alias, Expression<Func<R, IEnumerable<T>>> parentProperty)
    {
      return AddFetchingPart<R>(RetrieveNameFrom(property), alias, RetrieveNameFrom(parentProperty), false, false);
    }

    private IFetchingPart<R> AddFetchingPart<R>(string property, string alias, string parentProperty, bool isList, bool isInverse)
    {
      var existingFetchingPart = _children.FirstOrDefault(p => p.PropertyName == property && p.ParentPropertyName == parentProperty && p.IsList == isList);
      if (existingFetchingPart != null)
        return existingFetchingPart as IFetchingPart<R>;

      var fetchingPart = new FetchingPart<R>(this, alias, typeof (R), property, isList,parentProperty, false, isInverse);
      _children.Add(fetchingPart);

      IncrementChangesCount();

      return fetchingPart;
    }

    public void IncrementChangesCount()
    {
      ChangesCount++;
      if (!_isRoot)
        _parent.IncrementChangesCount();
    }

    public void ResetChangesCount()
    {
      ChangesCount = 0;
      Children.ForEach(p => p.ResetChangesCount());
    }

    public IFetchingPart<R> BackTo<R>(string alias)
    {
      IFetchingPart root = this.CalculateThe(new RootFetchingPartInHierarchy());
      return root.SearchInChildren<R>(alias);
    }

    public IFetchingPart<R> SearchInChildren<R>(string alias)
    {
      if (_alias == alias)
        return this as IFetchingPart<R>;

      IFetchingPart<R> ret = null;
      foreach (var part in _children)
      {
        ret = part.SearchInChildren<R>(alias);
        if (ret != null)
          break;
      }

      return ret;
    }

    public Type PartType
    {
      get { return _partType; }
    }

    public string PropertyName
    {
      get { return _propertyName; }
    }

    public string Alias
    {
      get { return _alias; }
    }

    public IEnumerable<IFetchingPart> EntityChildren
    {
      get { return _children.Where(p => !p.IsList); }
    }

    public IEnumerable<IFetchingPart> ListChildren
    {
      get { return _children.Where(p => p.IsList); }
    }

    public IFetchingPart Parent
    {
      get
      {
        return _parent;
      }
    }
  }
}